/*
 * Piso.cpp
 *
 */

#include "Piso.h"

/**
 * Contructor del piso.
 * Recibe los parametros de ejecucion del Piso.
 */
Piso::Piso( int nroPiso, int cantidadPisos )
	: nroPiso ( nroPiso) ,
	  cantidadPisos ( cantidadPisos ),
	  realizarLlamadas ( true ),
	  totalPersonas ( 0 ),
	  colaAscensor ( Cola<Persona>() ),
	  colaControlador ( Cola<Llamada>() ){
}

/**
 * Metodo que inicializa los mecanismos de concurrencia que usa el Piso.
 * Registra los handlers de seniales y crea las colas para la comunicacion
 * con el controlador y con los ascensores.
 */
int Piso::inicializar(){
	int retorno = 0;
	srand ( getMilisegundos() );
	SignalHandler :: getInstance()->registrarHandler( SIGUSR2, this );
	SignalHandler :: getInstance()->registrarHandler( SIGINT, this );
	SignalHandler :: getInstance()->registrarHandler( SIGTERM, this );
	SignalHandler :: getInstance()->registrarHandler( SIGABRT, this );

	char c = 1;
	for ( int i = 0 ; i < this->nroPiso; i++ ){
		c++;
	}
	if (colaAscensor.crear((char *)ARCHIVO_COLA_MSG_PA, c) != MSG_OK) {
		retorno = -1;
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": FALLO durante la creacion.");
	}
	else if (colaControlador.crear((char *)ARCHIVO_COLA_MSG_PC, ID_COLA_MSG) != MSG_OK) {
		retorno = -1;
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": FALLO durante la creacion.");
	}
	else {
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": creado.");
	}

	return retorno;
}

/**
 * Destructor del piso.
 * Elimina los handlers de seniales.
 *
 */
Piso::~Piso() {
	SignalHandler::getInstance()->removerHandler ( SIGUSR2 );
	SignalHandler::getInstance()->removerHandler ( SIGINT );
	SignalHandler::getInstance()->removerHandler ( SIGTERM );
	SignalHandler::getInstance()->removerHandler ( SIGABRT );
	SignalHandler::destruir();

	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": destruido.");
}

/**
 * Handler de seniales:
 * SIGUSR2: Senial que envia el terminador para que el piso deje de
 * 		generar personas.
 * SIGINT: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGTERM: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGABRT: Senial que envia el terminador en caso de que haya errores.
 * 		Se finaliza la ejecucion inmediatamente liberando los recursos.
 *
 */
int Piso::handleSignal ( int signum ) {
	if ( signum == SIGUSR2 ){
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGUSR2, no genero mas llamadas.");
		this->realizarLlamadas=false;

	} else if ( signum == SIGINT ){
		// Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGINT, no hago nada.");
		// El terminador (padre) se encarga de agarrar la senial.

	} else if ( signum == SIGTERM ){
		//Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGTERM, no hago nada.");
		// El terminador (padre) se encarga de agarrar la senial.

	} else if ( signum == SIGABRT ){
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGABRT, finalizo ante errores.");
		this->~Piso();
		exit( -1 );

	}
	return 0;
}

/**
 * Metodo que se ejecuta de forma continua simulando el arribo aleatorio
 * de personas al piso.
 *
 */
void Piso::generarLlamadas(){
	usleep ( this->nroPiso * 100000 + calcularRandom ( PERIODO_MINIMO_LLAMADAS, PERIODO_MAXIMO_LLAMADAS) * 1000000 );
	while ( this->realizarLlamadas ){
		Persona persona = this->construirPersona();
		this->realizarLlamada( persona );
		sleep ( calcularRandom ( PERIODO_MINIMO_LLAMADAS, PERIODO_MAXIMO_LLAMADAS ) );
	}
	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": total de personas que arribaron: " + intToString( this->totalPersonas ) );
}

/**
 * Metodo que devuelve una persona que arriba al piso.
 *
 */
Persona Piso::construirPersona(){
	int pisoDestino = this->calcularPisoDestino();
	Persona persona;
	persona.pisoDestino = pisoDestino ;
	persona.piso = this->nroPiso;
	persona.uid = this->totalPersonas;
	return persona;
}

/**
 * Metodo que escribe la persona en la cola de mensajes con el ascensor,
 * y escribe la llamada en la cola de mensajes del controlador
 *
 */
void Piso::realizarLlamada( Persona persona ){
	Llamada llamada;
	llamada.mtype = 1;
	llamada.piso = this->nroPiso ;
	if ( persona.pisoDestino > this->nroPiso ) {
		persona.mtype = LLAMADA_SUBIR;
		llamada.tipoLlamada = LLAMADA_SUBIR;
	} else {
		persona.mtype = LLAMADA_BAJAR;
		llamada.tipoLlamada = LLAMADA_BAJAR;
	}
	this->totalPersonas++;

	this->colaAscensor.escribir(persona);
	Log::getInstance()->imprimir("Piso " + intToString( this->nroPiso ) + ": llega la persona " + intToString(persona.piso) + "-" + intToString(persona.uid) + " que va al piso " + intToString( persona.pisoDestino ) + ".");
	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": meto una llamada al ascensor en la cola de mensajes.");
	this->colaControlador.escribir(llamada);

}

/**
 * Calcula un numero random que no coincide con el numero de piso actual.
 *
 */
int Piso::calcularPisoDestino(){
	int pisoDestino;
	do {
		pisoDestino = calcularRandom ( this->cantidadPisos );
	} while ( pisoDestino == this->nroPiso );
	return pisoDestino;
}

